1 /*------------------------------------------------------------------------
2 * Vulkan Conformance Tests
3 * ------------------------
4 *
5 * Copyright (c) 2019 Google LLC
6 * Copyright (c) 2019 The Khronos Group Inc.
7 *
8 * Licensed under the Apache License, Version 2.0 (the "License");
9 * you may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 *
20 *//*!
21 * \file
22 * \brief Functional tests using amber
23 *//*--------------------------------------------------------------------*/
24
25 #include <amber/amber.h>
26 #include "amber/recipe.h"
27
28 #include <iostream>
29
30 #include "deDefs.hpp"
31 #include "deUniquePtr.hpp"
32 #include "deFilePath.hpp"
33 #include "vktTestCaseUtil.hpp"
34 #include "tcuTestLog.hpp"
35 #include "vktAmberTestCase.hpp"
36 #include "vktAmberHelper.hpp"
37 #include "tcuResource.hpp"
38 #include "tcuTestLog.hpp"
39 #include "vkSpirVProgram.hpp"
40 #include "vkImageUtil.hpp"
41
42 namespace vkt
43 {
44 namespace cts_amber
45 {
46
AmberTestCase(tcu::TestContext & testCtx,const char * name,const char * description,const std::string & readFilename)47 AmberTestCase::AmberTestCase (tcu::TestContext& testCtx,
48 const char* name,
49 const char* description,
50 const std::string& readFilename)
51 : TestCase(testCtx, name, description),
52 m_recipe(DE_NULL),
53 m_readFilename(readFilename)
54 {
55 }
56
~AmberTestCase(void)57 AmberTestCase::~AmberTestCase (void)
58 {
59 delete m_recipe;
60 }
61
createInstance(Context & ctx) const62 TestInstance* AmberTestCase::createInstance (Context& ctx) const
63 {
64 return new AmberTestInstance(ctx, m_recipe);
65 }
66
createEngineConfig(Context & ctx)67 static amber::EngineConfig* createEngineConfig (Context& ctx)
68 {
69 amber::EngineConfig* vkConfig = GetVulkanConfig(ctx.getInstance(),
70 ctx.getPhysicalDevice(), ctx.getDevice(), &ctx.getDeviceFeatures(),
71 &ctx.getDeviceFeatures2(), ctx.getInstanceExtensions(),
72 ctx.getDeviceExtensions(), ctx.getUniversalQueueFamilyIndex(),
73 ctx.getUniversalQueue(), ctx.getInstanceProcAddr());
74
75 return vkConfig;
76 }
77
78 // Returns true if the given feature is supported by the device.
79 // Throws an internal error If the feature is not recognized at all.
isFeatureSupported(const vkt::Context & ctx,const std::string & feature)80 static bool isFeatureSupported(const vkt::Context& ctx, const std::string& feature)
81 {
82 if (feature == "Features.shaderInt16")
83 return ctx.getDeviceFeatures().shaderInt16;
84 if (feature == "Features.shaderInt64")
85 return ctx.getDeviceFeatures().shaderInt64;
86 if (feature == "Features.tessellationShader")
87 return ctx.getDeviceFeatures().tessellationShader;
88 if (feature == "Features.geometryShader")
89 return ctx.getDeviceFeatures().geometryShader;
90 if (feature == "Features.fragmentStoresAndAtomics")
91 return ctx.getDeviceFeatures().fragmentStoresAndAtomics;
92 if (feature == "Features.vertexPipelineStoresAndAtomics")
93 return ctx.getDeviceFeatures().vertexPipelineStoresAndAtomics;
94 if (feature == "Features.fillModeNonSolid")
95 return ctx.getDeviceFeatures().fillModeNonSolid;
96 if (feature == "Features.shaderStorageImageMultisample")
97 return ctx.getDeviceFeatures().shaderStorageImageMultisample;
98 if (feature == "VariablePointerFeatures.variablePointersStorageBuffer")
99 return ctx.getVariablePointersFeatures().variablePointersStorageBuffer;
100 if (feature == "VariablePointerFeatures.variablePointers")
101 return ctx.getVariablePointersFeatures().variablePointers;
102 if (feature == "SubgroupProperties.supportedStages.fragment")
103 return (ctx.getSubgroupProperties().supportedStages & vk::VK_SHADER_STAGE_FRAGMENT_BIT) != 0;
104 if (feature == "SubgroupProperties.supportedOperations.vote")
105 return (ctx.getSubgroupProperties().supportedOperations & vk::VK_SUBGROUP_FEATURE_VOTE_BIT) != 0;
106 if (feature == "SubgroupProperties.supportedOperations.ballot")
107 return (ctx.getSubgroupProperties().supportedOperations & vk::VK_SUBGROUP_FEATURE_BALLOT_BIT) != 0;
108
109 std::string message = std::string("Unexpected feature name: ") + feature;
110 TCU_THROW(InternalError, message.c_str());
111 }
112
delayedInit(void)113 void AmberTestCase::delayedInit(void)
114 {
115 // Make sure the input can be parsed before we use it.
116 if (!parse(m_readFilename))
117 {
118 std::string message = "Failed to parse Amber file: " + m_readFilename;
119 TCU_THROW(InternalError, message.c_str());
120 }
121 }
122
checkSupport(Context & ctx) const123 void AmberTestCase::checkSupport(Context& ctx) const
124 {
125 // Check for instance and device extensions as declared by the test code.
126 if (m_required_extensions.size())
127 {
128 std::set<std::string> device_extensions(ctx.getDeviceExtensions().begin(),
129 ctx.getDeviceExtensions().end());
130 std::set<std::string> instance_extensions(ctx.getInstanceExtensions().begin(),
131 ctx.getInstanceExtensions().end());
132 std::string missing;
133 for (std::set<std::string>::iterator iter = m_required_extensions.begin();
134 iter != m_required_extensions.end();
135 ++iter)
136 {
137 const std::string extension = *iter;
138 if ((device_extensions.count(extension) == 0) &&
139 (instance_extensions.count(extension) == 0))
140 {
141 missing += " " + extension;
142 }
143 }
144 if (missing.size() > 0)
145 {
146 std::string message("Test requires unsupported extensions:");
147 message += missing;
148 TCU_THROW(NotSupportedError, message.c_str());
149 }
150 }
151
152 // Check for required features. Do this after extensions are checked because
153 // some feature checks are only valid when corresponding extensions are enabled.
154 if (m_required_features.size())
155 {
156 std::string missing;
157 for (std::set<std::string>::iterator iter = m_required_features.begin();
158 iter != m_required_features.end();
159 ++iter)
160 {
161 const std::string feature = *iter;
162 if (!isFeatureSupported(ctx, feature))
163 {
164 missing += " " + feature;
165 }
166 }
167 if (missing.size() > 0)
168 {
169 std::string message("Test requires unsupported features:");
170 message += missing;
171 TCU_THROW(NotSupportedError, message.c_str());
172 }
173 }
174
175 for (auto req : m_imageRequirements)
176 checkImageSupport(ctx.getInstanceInterface(), ctx.getPhysicalDevice(), req);
177
178 for (auto req : m_bufferRequirements)
179 {
180 vk::VkFormatProperties prop;
181 ctx.getInstanceInterface().getPhysicalDeviceFormatProperties(ctx.getPhysicalDevice(), req.m_format, &prop);
182
183 if ((req.m_featureFlags & prop.bufferFeatures) != req.m_featureFlags)
184 {
185 TCU_THROW(NotSupportedError, "Buffer format doesn't support required feature flags");
186 }
187 }
188
189 if (m_name == "triangle_fan" &&
190 ctx.isDeviceFunctionalitySupported("VK_KHR_portability_subset") &&
191 !ctx.getPortabilitySubsetFeatures().triangleFans)
192 {
193 TCU_THROW(NotSupportedError, "VK_KHR_portability_subset: Triangle fans are not supported by this implementation");
194 }
195 }
196
197 class Delegate : public amber::Delegate
198 {
199 public:
200 Delegate (tcu::TestContext& testCtx);
201
202 amber::Result LoadBufferData (const std::string file_name,
203 amber::BufferDataFileType file_type,
204 amber::BufferInfo* buffer) const override;
205
Log(const std::string &)206 void Log (const std::string& /*message*/) override { DE_FATAL("amber::Delegate::Log unimplemented"); }
LogGraphicsCalls(void) const207 bool LogGraphicsCalls (void) const override { return m_logGraphicsCalls; }
SetLogGraphicsCalls(bool log_graphics_calls)208 void SetLogGraphicsCalls (bool log_graphics_calls) { m_logGraphicsCalls = log_graphics_calls; }
LogExecuteCalls(void) const209 bool LogExecuteCalls (void) const override { return m_logExecuteCalls; }
SetLogExecuteCalls(bool log_execute_calls)210 void SetLogExecuteCalls (bool log_execute_calls) { m_logExecuteCalls = log_execute_calls; }
LogGraphicsCallsTime(void) const211 bool LogGraphicsCallsTime (void) const override { return m_logGraphicsCallsTime; }
SetLogGraphicsCallsTime(bool log_graphics_calls_time)212 void SetLogGraphicsCallsTime (bool log_graphics_calls_time) { m_logGraphicsCallsTime = log_graphics_calls_time; }
GetTimestampNs(void) const213 deUint64 GetTimestampNs (void) const override { DE_FATAL("amber::Delegate::GetTimestampNs unimplemented"); return 0; }
SetScriptPath(std::string path)214 void SetScriptPath (std::string path) { m_path = path; }
215
216 private:
217 tcu::TestContext& m_testCtx;
218 std::string m_path;
219 bool m_logGraphicsCalls;
220 bool m_logGraphicsCallsTime;
221 bool m_logExecuteCalls;
222 };
223
Delegate(tcu::TestContext & testCtx)224 Delegate::Delegate (tcu::TestContext& testCtx)
225 : m_testCtx (testCtx)
226 , m_path ("")
227 , m_logGraphicsCalls (false)
228 , m_logGraphicsCallsTime (false)
229 , m_logExecuteCalls (false)
230 {
231 }
232
LoadBufferData(const std::string file_name,amber::BufferDataFileType file_type,amber::BufferInfo * buffer) const233 amber::Result Delegate::LoadBufferData (const std::string file_name,
234 amber::BufferDataFileType file_type,
235 amber::BufferInfo* buffer) const
236 {
237 const tcu::Archive& archive = m_testCtx.getArchive();
238 const de::FilePath filePath = de::FilePath(m_path).join(file_name);
239 de::UniquePtr<tcu::Resource> file (archive.getResource(filePath.getPath()));
240 int numBytes = file->getSize();
241 std::vector<deUint8> bytes (numBytes);
242
243 if (file_type == amber::BufferDataFileType::kPng)
244 return amber::Result("Amber PNG loading unimplemented");
245
246 file->read(bytes.data(), numBytes);
247
248 if (bytes.empty())
249 return amber::Result("Failed to load buffer data " + file_name);
250
251 for (deUint8 byte : bytes)
252 {
253 amber::Value value;
254 value.SetIntValue(static_cast<deUint64>(byte));
255 buffer->values.push_back(value);
256 }
257
258 buffer->width = 1;
259 buffer->height = 1;
260
261 return {};
262 }
263
parse(const std::string & readFilename)264 bool AmberTestCase::parse (const std::string& readFilename)
265 {
266 std::string script = ShaderSourceProvider::getSource(m_testCtx.getArchive(), readFilename.c_str());
267 if (script.empty())
268 return false;
269
270 Delegate delegate (m_testCtx);
271 delegate.SetScriptPath(de::FilePath(readFilename).getDirName());
272
273 m_recipe = new amber::Recipe();
274
275 amber::Amber am (&delegate);
276 amber::Result r = am.Parse(script, m_recipe);
277
278 m_recipe->SetFenceTimeout(~0u); // infinity of miliseconds
279
280 if (!r.IsSuccess())
281 {
282 getTestContext().getLog()
283 << tcu::TestLog::Message
284 << "Failed to parse Amber test "
285 << readFilename
286 << ": "
287 << r.Error()
288 << "\n"
289 << tcu::TestLog::EndMessage;
290 // TODO(dneto): Enhance Amber to not require this.
291 m_recipe->SetImpl(DE_NULL);
292 return false;
293 }
294 return true;
295 }
296
initPrograms(vk::SourceCollections & programCollection) const297 void AmberTestCase::initPrograms (vk::SourceCollections& programCollection) const
298 {
299 std::vector<amber::ShaderInfo> shaders = m_recipe->GetShaderInfo();
300 for (size_t i = 0; i < shaders.size(); ++i)
301 {
302 const amber::ShaderInfo& shader = shaders[i];
303
304 vk::SpirvVersion spirvVersion = vk::SPIRV_VERSION_1_0;
305 DE_STATIC_ASSERT(vk::SPIRV_VERSION_LAST == vk::SPIRV_VERSION_1_5 + 1);
306 if (shader.target_env == "spv1.5")
307 spirvVersion = vk::SPIRV_VERSION_1_5;
308 else if (shader.target_env == "spv1.4")
309 spirvVersion = vk::SPIRV_VERSION_1_4;
310 else if (shader.target_env == "spv1.3")
311 spirvVersion = vk::SPIRV_VERSION_1_3;
312 else if (shader.target_env == "spv1.2")
313 spirvVersion = vk::SPIRV_VERSION_1_2;
314 else if (shader.target_env == "spv1.1")
315 spirvVersion = vk::SPIRV_VERSION_1_1;
316
317 /* Hex encoded shaders do not need to be pre-compiled */
318 if (shader.format == amber::kShaderFormatSpirvHex)
319 continue;
320
321 if (shader.format == amber::kShaderFormatSpirvAsm)
322 {
323 programCollection.spirvAsmSources.add(shader.shader_name) << shader.shader_source << m_asm_options;
324 }
325 else if (shader.format == amber::kShaderFormatGlsl)
326 {
327 switch (shader.type)
328 {
329 case amber::kShaderTypeCompute:
330 programCollection.glslSources.add(shader.shader_name)
331 << glu::ComputeSource(shader.shader_source)
332 << vk::ShaderBuildOptions(programCollection.usedVulkanVersion, spirvVersion, 0u);
333 break;
334 case amber::kShaderTypeGeometry:
335 programCollection.glslSources.add(shader.shader_name)
336 << glu::GeometrySource(shader.shader_source)
337 << vk::ShaderBuildOptions(programCollection.usedVulkanVersion, spirvVersion, 0u);
338 break;
339 case amber::kShaderTypeFragment:
340 programCollection.glslSources.add(shader.shader_name)
341 << glu::FragmentSource(shader.shader_source)
342 << vk::ShaderBuildOptions(programCollection.usedVulkanVersion, spirvVersion, 0u);
343 break;
344 case amber::kShaderTypeVertex:
345 programCollection.glslSources.add(shader.shader_name)
346 << glu::VertexSource(shader.shader_source)
347 << vk::ShaderBuildOptions(programCollection.usedVulkanVersion, spirvVersion, 0u);
348 break;
349 case amber::kShaderTypeTessellationControl:
350 programCollection.glslSources.add(shader.shader_name)
351 << glu::TessellationControlSource(shader.shader_source)
352 << vk::ShaderBuildOptions(programCollection.usedVulkanVersion, spirvVersion, 0u);
353 break;
354 case amber::kShaderTypeTessellationEvaluation:
355 programCollection.glslSources.add(shader.shader_name)
356 << glu::TessellationEvaluationSource(shader.shader_source)
357 << vk::ShaderBuildOptions(programCollection.usedVulkanVersion, spirvVersion, 0u);
358 break;
359 case amber::kShaderTypeMulti:
360 DE_ASSERT(false && "Multi shaders not supported");
361 break;
362 }
363 }
364 else
365 {
366 DE_ASSERT(false && "Shader format not supported");
367 }
368 }
369 }
370
iterate(void)371 tcu::TestStatus AmberTestInstance::iterate (void)
372 {
373 amber::Amber am (DE_NULL);
374 amber::Options amber_options;
375 amber::ShaderMap shaderMap;
376 amber::Result r;
377
378 amber_options.engine = amber::kEngineTypeVulkan;
379 amber_options.config = createEngineConfig(m_context);
380 amber_options.execution_type = amber::ExecutionType::kExecute;
381
382 // Check for extensions as declared by the Amber script itself. Throw an internal
383 // error if that's more demanding.
384 r = am.AreAllRequirementsSupported(m_recipe, &amber_options);
385 if (!r.IsSuccess())
386 {
387 // dEQP does not to rely on external code to determine whether
388 // a test is supported. So throw an internal error here instead
389 // of a NotSupportedError. If an Amber test is not supported, then
390 // you must override this method and throw a NotSupported exception
391 // before reach here.
392 TCU_THROW(InternalError, r.Error().c_str());
393 }
394
395 std::vector<amber::ShaderInfo> shaders = m_recipe->GetShaderInfo();
396 for (size_t i = 0; i < shaders.size(); ++i)
397 {
398 const amber::ShaderInfo& shader = shaders[i];
399
400 if (!m_context.getBinaryCollection().contains(shader.shader_name))
401 continue;
402
403 size_t len = m_context.getBinaryCollection().get(shader.shader_name).getSize();
404 /* This is a compiled spir-v binary which must be made of 4-byte words. We
405 * are moving into a word sized vector so divide by 4
406 */
407 std::vector<deUint32> data;
408 data.resize(len >> 2);
409 deMemcpy(data.data(), m_context.getBinaryCollection().get(shader.shader_name).getBinary(), len);
410
411 shaderMap[shader.shader_name] = data;
412 }
413
414 r = am.ExecuteWithShaderData(m_recipe, &amber_options, shaderMap);
415 if (!r.IsSuccess()) {
416 m_context.getTestContext().getLog()
417 << tcu::TestLog::Message
418 << r.Error()
419 << "\n"
420 << tcu::TestLog::EndMessage;
421 }
422
423 delete amber_options.config;
424
425 return r.IsSuccess() ? tcu::TestStatus::pass("Pass") :tcu::TestStatus::fail("Fail");
426 }
427
setSpirVAsmBuildOptions(const vk::SpirVAsmBuildOptions & asm_options)428 void AmberTestCase::setSpirVAsmBuildOptions (const vk::SpirVAsmBuildOptions& asm_options)
429 {
430 m_asm_options = asm_options;
431 }
432
addRequirement(const std::string & requirement)433 void AmberTestCase::addRequirement (const std::string& requirement)
434 {
435 if (requirement.find(".") != std::string::npos)
436 m_required_features.insert(requirement);
437 else
438 m_required_extensions.insert(requirement);
439 }
440
addImageRequirement(vk::VkImageCreateInfo info)441 void AmberTestCase::addImageRequirement (vk::VkImageCreateInfo info)
442 {
443 m_imageRequirements.push_back(info);
444 }
445
addBufferRequirement(BufferRequirement req)446 void AmberTestCase::addBufferRequirement (BufferRequirement req)
447 {
448 m_bufferRequirements.push_back(req);
449 }
450
451 } // cts_amber
452 } // vkt
453