| /*------------------------------------------------------------------------ |
| * 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 <amber/amber.h> |
| #include "amber/recipe.h" |
| |
| #include <iostream> |
| |
| #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, nullptr); |
| } |
| |
| static amber::EngineConfig* createEngineConfig (Context& ctx, vk::VkDevice customDevice) |
| { |
| vk::VkDevice dev = customDevice != nullptr ? customDevice : ctx.getDevice(); |
| vk::VkQueue queue; |
| vk::DeviceDriver vk(ctx.getPlatformInterface(), ctx.getInstance(), dev); |
| vk.getDeviceQueue(dev, ctx.getUniversalQueueFamilyIndex(), 0, &queue); |
| |
| amber::EngineConfig* vkConfig = GetVulkanConfig(ctx.getInstance(), |
| ctx.getPhysicalDevice(), dev, &ctx.getDeviceFeatures(), |
| &ctx.getDeviceFeatures2(), ctx.getInstanceExtensions(), |
| ctx.getDeviceExtensions(), ctx.getUniversalQueueFamilyIndex(), |
| queue, 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 == "Float16Int8Features.shaderInt8") |
| return ctx.getShaderFloat16Int8Features().shaderInt8; |
| 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.depthClamp") |
| return ctx.getDeviceFeatures().depthClamp; |
| if (feature == "Features.tessellationShader") |
| return ctx.getDeviceFeatures().tessellationShader; |
| if (feature == "Features.shaderTessellationAndGeometryPointSize") |
| return ctx.getDeviceFeatures().shaderTessellationAndGeometryPointSize; |
| 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.basic") |
| return (ctx.getSubgroupProperties().supportedOperations & vk::VK_SUBGROUP_FEATURE_BASIC_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; |
| if (feature == "Storage8BitFeatures.storageBuffer8BitAccess") |
| return ctx.get8BitStorageFeatures().storageBuffer8BitAccess; |
| |
| 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<std::string> device_extensions(ctx.getDeviceExtensions().begin(), |
| ctx.getDeviceExtensions().end()); |
| std::set<std::string> instance_extensions(ctx.getInstanceExtensions().begin(), |
| ctx.getInstanceExtensions().end()); |
| std::string missing; |
| for (std::set<std::string>::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<std::string>::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"); |
| } |
| } |
| |
| if (m_checkSupportCallback) |
| (m_checkSupportCallback)(ctx, m_name); |
| } |
| |
| 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<tcu::Resource> file (archive.getResource(filePath.getPath())); |
| int numBytes = file->getSize(); |
| std::vector<deUint8> 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<deUint64>(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<amber::ShaderInfo> 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_6 + 1); |
| if (shader.target_env == "spv1.6") |
| spirvVersion = vk::SPIRV_VERSION_1_6; |
| else 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, m_customDevice); |
| 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<amber::ShaderInfo> 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<deUint32> 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<std::string> allRequirements; |
| allRequirements.insert(begin(deviceExtensions), end(deviceExtensions)); |
| allRequirements.insert(begin(instanceExtensions), end(instanceExtensions)); |
| allRequirements.insert(begin(requiredFeatures), end(requiredFeatures)); |
| |
| std::set<std::string> 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; |
| |
| // Repeat message for cerr so it's visible in console log. |
| std::cerr << "ERROR: CTS and Amber test requirement mismatch.\n"; |
| std::cerr << "Amber filename: " << m_readFilename << "\n"; |
| std::cerr << "CTS requirements:\n"; |
| for (const auto& ctsReq : ctsRequirements) |
| std::cerr << " " << ctsReq << "\n"; |
| |
| std::cerr << "Amber requirements:\n"; |
| for (const auto& amberReq : allRequirements) |
| std::cerr << " " << amberReq << "\n"; |
| |
| return false; |
| } |
| return true; |
| } |
| |
| } // cts_amber |
| } // vkt |